using System;
using System.Diagnostics;
using System.Threading;
using DotNetRepository.Cache;
using DotNetRepository.Tools;

namespace DotNetRepository.LinqToSql
{
    internal class EntityCache<TEntity> where TEntity : class, IEntity
    {
        public static readonly EntityCache<TEntity> Instance = new EntityCache<TEntity>();

        private readonly CacheManager<Guid,EntityCacheItem<TEntity>> _cache;
        private readonly CacheManager<Guid,WeakReference<EntityCacheItem<TEntity>>> _expiredCache;

        internal ReaderWriterLockSlim CacheLock = new ReaderWriterLockSlim();
        internal ValueLock<Guid> GuidLock = new ValueLock<Guid>();
        private readonly bool _strongCacheEnabled;
        
        private EntityCache()
        {
            _cache = new CacheManager<Guid, EntityCacheItem<TEntity>>((int)Settings.Default.CachePollFrequency.TotalMilliseconds, 0);
            _expiredCache = new CacheManager<Guid, WeakReference<EntityCacheItem<TEntity>>>((int)Settings.Default.CachePollFrequency.TotalMilliseconds, 0);
            _strongCacheEnabled = !(
                                      Settings.Default.DefaultCacheInitialExpiration.TotalMilliseconds == 0 &&
                                      Settings.Default.DefaultCacheMaxExpiration.TotalMilliseconds == 0);
        }

        internal void Add(EntityCacheItem<TEntity> item)
        {
            if (!_strongCacheEnabled)
            {
                AddWeak(item);
                return;
            }

            using(GuidLock.Lock(item.Entity.ID))
            {
                _cache.Add(item.Entity.ID, item, CacheItemPriority.Normal,
                          new EntityCacheRefreshAction<TEntity>(), new EntityCacheItemExpiration<TEntity>());
            }
            if (Settings.Default.EnableCacheTrace)
                Debug.WriteLine(String.Format("{0} added to strong _cache.", item));
        }

        internal void AddWeak(EntityCacheItem<TEntity> item)
        {
            using(GuidLock.Lock(item.Entity.ID))
            {
                _expiredCache.Add(item.Entity.ID,
                                 new WeakReference<EntityCacheItem<TEntity>>(item), CacheItemPriority.NotRemovable, null,
                                 new EntityWeakReferenceCacheItemExpiration<TEntity>());
            }

            if (Settings.Default.EnableCacheTrace)
                Debug.WriteLine(String.Format("{0} added to weak _cache.", item));
        }

        /// <summary>
        /// Forces a full removal from the _cache (including WeakReferences).  This should only be used when an object is permanently deleted.
        /// </summary>
        /// <param name="item"></param>
        internal void ForceRemove(TEntity item)
        {
            using(GuidLock.Lock(item.ID))
            {
                _cache.Remove(item.ID);
                _expiredCache.Remove(item.ID);
            }
        }

        /// <summary>
        /// This method is automatically called by the EntityWeakReferenceCacheItemExpiration policy whenever an item
        /// in the WeakReferenceCache is touched.  Removes the item from the WeakReferenceCache and places it in the normal _cache.
        /// </summary>
        /// <param name="item"></param>
        internal void ElevateFromWeakCache(EntityCacheItem<TEntity> item)
        {
            if(!_strongCacheEnabled) return;

            using(GuidLock.Lock(item.Entity.ID))
            {
                ElevateFromWeakCacheNoLock(item);
            }
        }

        /// <summary>
        /// Only use this method when a lock on the ID has already been obtained
        /// </summary>
        /// <param name="item"></param>
        private void ElevateFromWeakCacheNoLock(EntityCacheItem<TEntity> item)
        {
            if (!_strongCacheEnabled) return;

            if (_expiredCache.Contains(item.Entity.ID))
            {
                _expiredCache.Remove(item.Entity.ID);

                _cache.Add(item.Entity.ID, item, CacheItemPriority.Normal,
                          new EntityCacheRefreshAction<TEntity>(), new EntityCacheItemExpiration<TEntity>());
            }
            if (Settings.Default.EnableCacheTrace)
                Debug.WriteLine(String.Format("{0} elevated from weak _cache to strong _cache.", item));
        }

        public bool Contains(Guid id)
        {
            using(GuidLock.Lock(id))
            {
                if (_strongCacheEnabled && _cache.Contains(id))
                    return true;

                if (_expiredCache.Contains(id))
                {
                    var weakRef = _expiredCache.GetValue(id);
                    if (weakRef == null)
                        return false;

                    if (weakRef.IsAlive)
                        return true;

                    _expiredCache.Remove(id);
                    return false;
                }
                return false;
            }
        }

        internal EntityCacheItem<TEntity> GetData(Guid id)
        {
            using(GuidLock.Lock(id))
            {
                if (_strongCacheEnabled)
                {
                    var itm = _cache.GetValue(id);
                    if (itm != null)
                    {
                        if (Settings.Default.EnableCacheTrace)
                            Debug.WriteLine(String.Format("{0} retrieved from strong _cache.", itm));

                        return itm;
                    } 
                }

                var weakRef = _expiredCache.GetValue(id);
                if (weakRef == null)
                {
                    if (Settings.Default.EnableCacheTrace)
                        Debug.WriteLine(String.Format("{0} ({1}) not found in either _cache.", typeof (TEntity).Name, id));
                    return null;
                }

                var item = weakRef.Target;
                if (item == null)
                {
                    if (Settings.Default.EnableCacheTrace)
                        Debug.WriteLine(String.Format("{0} ({1}) found but has been garbage collected.",
                                                      typeof (TEntity).Name, id));
                    
                    _expiredCache.Remove(id);
                    return null;
                }

                if (Settings.Default.EnableCacheTrace)
                    Debug.WriteLine(String.Format("{0} retrieved from weak _cache.", item));

                ElevateFromWeakCacheNoLock(item);
                return item;
            }
        }
    }
}